package com.css.gdzj.gdzjbxgd.common.util.http;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Author WT
 * @Date 2020/8/10
 */
public class HttpUtils_withPooling {

    private static final Logger LOG = LoggerFactory.getLogger(HttpUtils_withPooling.class);
    //配置连接访问和数据传输等配置项的超时时间
    private static final int CONNECT_TIMEOUT = 100;    //设置超时毫秒数
    private static final int SOCKET_TIMEOUT = 300000;    //设置传输毫秒数
    private static final int REQUESTCONNECT_TIMEOUT = 100;        //获取请求超时毫秒数
    private static final int CONNECT_TOTAL = 200;    //最大连接数
    private static final int CONNECT_ROUTE = 20;    //设置每个路由的基础连接数
    private static final int VALIDATE_TIME = 120000;    //设置重用连接时间
    private static final String RESPONSE_CONTENT = "通信失败";


    //创建连接池和客户端连接实例
    private static PoolingHttpClientConnectionManager manager = null;
    private static CloseableHttpClient client = null;

    //配置连接池参数
    static {
        ConnectionSocketFactory csf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory lcsf = createSSLConnSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", csf)
                .register("https", lcsf)
                .build();

        manager = new PoolingHttpClientConnectionManager(registry);
        manager.setMaxTotal(CONNECT_TOTAL);
        manager.setDefaultMaxPerRoute(CONNECT_ROUTE);
        manager.setValidateAfterInactivity(VALIDATE_TIME);
        SocketConfig config = SocketConfig.custom().setSoTimeout(SOCKET_TIMEOUT).build();
        manager.setDefaultSocketConfig(config);
        RequestConfig requestConf = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT)
                .setConnectionRequestTimeout(REQUESTCONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
        client = HttpClients.custom().setConnectionManager(manager).setDefaultRequestConfig(requestConf).setRetryHandler(

                // 实现了HttpRequestRetryHandler接口的
                // public boolean retryRequest(IOException exception, int executionCount, HttpContext context)方法
                (exception, executionCount, context) -> {
                    if(executionCount >= 3)
                        return false;
                    if(exception instanceof NoHttpResponseException)    //如果服务器断掉了连接那么重试
                        return true;
                    if(exception instanceof SSLHandshakeException)    //不重试握手异常
                        return false;
                    if(exception instanceof InterruptedIOException)    //IO传输中断重试
                        return true;
                    if(exception instanceof UnknownHostException)    //未知服务器
                        return false;
                    if(exception instanceof ConnectTimeoutException)    //超时就重试
                        return true;
                    if(exception instanceof SSLException)
                        return false;

                    HttpClientContext cliContext = HttpClientContext.adapt(context);
                    HttpRequest request = cliContext.getRequest();
                    if(!(request instanceof HttpEntityEnclosingRequest))
                        return true;
                    return false;
                }).build();

        if( manager != null && manager.getTotalStats() != null )
            LOG.info("客户池状态："+manager.getTotalStats().toString());
    }

    //创建SSLConnectionSocketFactory配置方法
    private static SSLConnectionSocketFactory createSSLConnSocketFactory() {
        SSLConnectionSocketFactory sslsf = null;
        SSLContext context;

        try {
            X509TrustManager x509m = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkServerTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                }

                public void checkClientTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                }
            };

            context = SSLContext.getInstance(SSLConnectionSocketFactory.SSL);

            // 初始化SSLContext实例
            try {
                //最关键的必须有这一步，否则抛出SSLContextImpl未被初始化的异常
                context.init(null, new TrustManager[] { x509m }, new java.security.SecureRandom());
            } catch (KeyManagementException e) {
                LOG.debug("SSL上下文初始化失败，由于 {}", e.getLocalizedMessage());
            }

            sslsf = new SSLConnectionSocketFactory(context, NoopHostnameVerifier.INSTANCE);
        } catch (NoSuchAlgorithmException e) {
            LOG.debug("SSL上下文创建失败，由于 {}", e.getLocalizedMessage());
        }
        return sslsf;
    }

    //发送请求、获取应当报文的方法
    private static String getResult(HttpRequestBase method) {
        HttpClientContext context = HttpClientContext.create();
        CloseableHttpResponse response = null;
        String content = RESPONSE_CONTENT;

        try {
            response = client.execute(method, context);    //执行GET/POST请求
            HttpEntity entity = response.getEntity();    //获取响应实体
            if(entity != null) {
                Charset charset = ContentType.getOrDefault(entity).getCharset();
                content = EntityUtils.toString(entity, charset);
                EntityUtils.consume(entity);
            }
        } catch(ConnectTimeoutException cte) {
            LOG.error("请求连接超时，由于 " + cte.getLocalizedMessage());
            cte.printStackTrace();
        } catch(SocketTimeoutException ste) {
            LOG.error("请求通信超时，由于 " + ste.getLocalizedMessage());
            ste.printStackTrace();
        } catch(ClientProtocolException cpe) {
            LOG.error("协议错误（如构造HttpGet对象时传入协议不对，将'http'写成'htpp' or 响应内容不符合），由于 " + cpe.getLocalizedMessage());
            cpe.printStackTrace();
        } catch(IOException ie) {
            LOG.error("实体转换异常或者网络异常，由于 " + ie.getLocalizedMessage());
            ie.printStackTrace();
        } finally {
            try {
                if(response != null) {
                    response.close();
                }
            } catch(IOException e) {
                LOG.error("响应关闭异常，由于 " + e.getLocalizedMessage());
            }
            if(method != null) {
                method.releaseConnection();
            }
        }

        return content;
    }


    /**
     * get请求
     *
     * @param url
     * @return get content
     */

    public static String doGet(String url) {
        HttpGet httpGet = new HttpGet(url);

        return getResult( httpGet );
    }

    /**
     * get请求
     *
     * @param url
     * @在方法内部封装HTTP Header信息
     * @return get content
     */
    public static String doGetWithHeader(String url) {
        HttpGet httpGet = new HttpGet(url);

        httpGet.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;");
        httpGet.addHeader("Accept-Language", "zh-cn");
        httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3");
        httpGet.addHeader("Keep-Alive", "300");
        httpGet.addHeader("Connection", "Keep-Alive");
        httpGet.addHeader("Cache-Control", "no-cache");

        return getResult( httpGet );
    }

    /**
     * post请求
     *
     * @param url
     * @param data with JSON format String
     * @return post content
     * @throws IOException
     */
    public static String doPost(String url, String data) throws IOException {
        if (null == url || "".equals(url)) {
            throw new NullPointerException("接口URL错误！");
        }
        HttpPost httpPost = new HttpPost(url);
        // 设置Header和公共参数
        httpPost.addHeader("content-type", "application/json");
        if( StringUtils.isNotBlank(data) ) {
            StringEntity entity = new StringEntity( data, "utf-8" );
            entity.setContentEncoding("UTF-8");
            httpPost.setEntity( entity );
        }
        return getResult( httpPost );
    }

    /**
     * post请求
     *
     * @param url
     * @return post content
     * @throws IOException
     */
    public static String doPost(String url, Map<String, String> param) throws IOException {
        if (null == url || "".equals(url)) {
            throw new NullPointerException("接口URL错误！");
        }

        if (null == param) {
            throw new NullPointerException("参数列表为空！");
        }

        HttpPost httpPost = new HttpPost(url);
        // 设置Header和公共参数
        httpPost.addHeader("content-type", "application/json");
        List<NameValuePair> nvpList = convertToNameValuePair(param);

        try {
            httpPost.setEntity(new UrlEncodedFormEntity( nvpList ));
        } catch (UnsupportedEncodingException e) {
            LOG.error("请求设置实体异常，不支持的字符集，由于 " + e.getLocalizedMessage());
            e.printStackTrace();
        }

        return getResult( httpPost );
    }

    /**
     * post请求
     *
     * @param url
     * @return post content
     * @throws IOException
     */
    public static String doPost(String url, JSONObject jsonObject) {
        if (null == url || "".equals(url)) {
            throw new NullPointerException("接口URL错误！");
        }

        if (null == jsonObject) {
            throw new NullPointerException("参数列表为空！");
        }

        HttpPost httpPost = new HttpPost(url);
        // 设置Header和公共参数
        httpPost.addHeader("content-type", "application/json");
        // 解决中文乱码问题
        StringEntity entity = new StringEntity( jsonObject.toString(), "utf-8" );
        entity.setContentEncoding("UTF-8");
        entity.setContentType("application/x-www-form-urlencoded");
        httpPost.setEntity(entity);

        return getResult( httpPost );
    }

   // 添加一个Map<String, String>转换成NameValuePair的方法用来处理Data Form格式的请求
    // 比之前的方法更好
    private static List<NameValuePair> convertToNameValuePair(Map<String, String> pairs) {
        List<NameValuePair> nvpList = new ArrayList<>(pairs.size());
        for (Map.Entry<String, String> entry : pairs.entrySet()) {
            nvpList.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));}
            return nvpList;
    }
}
